{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 古诗生成\n",
    "本项目使用RNN进行古诗生成，使用跟彩票预测相同的思路和网络，TensorFlow2.0实现。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## LSTM介绍\n",
    "我们需要从过往的历史数据中寻找规律，[`LSTM`](https://en.wikipedia.org/wiki/Long_short-term_memory)再适合不过了。如果你对LSTM不熟悉的话，以下几篇文章建议你阅读：\n",
    "\n",
    "[`Understanding LSTM Networks`](http://colah.github.io/posts/2015-08-Understanding-LSTMs/)\n",
    "\n",
    "[`[译] 理解 LSTM 网络`](http://www.jianshu.com/p/9dc9f41f0b29)\n",
    "\n",
    "[`RNN以及LSTM的介绍和公式梳理`](http://blog.csdn.net/Dark_Scope/article/details/47056361)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加载数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_dir = './data/newtxt.txt'  # new_poetry\n",
    "text = open(data_dir, 'rb').read().decode(encoding='utf-8')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面函数是用来处理初始数据集poetry.txt的，使用newtxt.txt可以不调用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import re\n",
    "pattern = '[a-zA-Z0-9’\"#$%&\\'()*+-./:;<=>@★…【】《》“”‘’[\\\\]^_`{|}~]+'\n",
    "\n",
    "def preprocess_poetry(outdir, datadir):\n",
    "    with open(os.path.join(outdir, 'new_poetry.txt'), 'w') as out_f:\n",
    "        with open(os.path.join(datadir, 'poetry.txt'), 'r') as f:\n",
    "            for line in f:\n",
    "                content = line.strip().rstrip('\\n').split(':')[1]  # .rstrip('\\n').\n",
    "                content = content.replace(' ','')\n",
    "                if '】' in content or '_' in content or '(' in content or '（' in content or '《' in content or '[' in content:\n",
    "                    continue\n",
    "                if len(content) < 20:\n",
    "                    continue\n",
    "                content=re.sub(pattern, '', content)\n",
    "                out_f.write(content + '\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "preprocess_poetry('./data/', './data/')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 预测网络介绍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "网络的输入是每一个汉字，总共有1020个字，用one hot编码是一个1020维的稀疏向量。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用one hot稀疏向量在输入层与网络第一层做矩阵乘法时会很没有效率，因为向量里面大部分都是0， 矩阵乘法浪费了大量的计算，最终矩阵运算得出的结果是向量中值为1的列所对应的矩阵中的行向量。\n",
    "<img src=\"assets/lookup_matrix.png\">\n",
    "\n",
    "这看起来很像用索引查表一样，one hot向量中值为1的位置作为下标，去索引参数矩阵中的行向量。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了代替矩阵乘法，我们将参数矩阵当作一个查找表（lookup table）或者叫做嵌入矩阵（embedding matrix），使用每个汉字所对应索引，比如汉字“你”，索引是958，然后在查找表中找第958行。\n",
    "\n",
    "这其实跟替换之前的模型没有什么不同，嵌入矩阵就是参数矩阵，嵌入层仍然是隐层。查找表只是矩阵乘法的一种便捷方式，它会像参数矩阵一样被训练，是要学习的参数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面就是我们要构建的网络架构，从嵌入层输出的向量进入LSTM层进行时间序列的学习，然后经过softmax预测出下一个汉字。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 编码实现"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 实现数据预处理\n",
    "\n",
    "我们需要先准备好汉字和ID之间的转换关系。在这个函数中，创建并返回两个字典：\n",
    "- 汉字到ID的转换字典： `vocab_to_int`\n",
    "- ID到汉字的转换字典： `int_to_vocab`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from collections import Counter\n",
    "import pickle\n",
    "\n",
    "def create_lookup_tables():\n",
    "\n",
    "    vocab = sorted(set(text))\n",
    "    vocab_to_int = {u:i for i, u in enumerate(vocab)}\n",
    "    int_to_vocab = np.array(vocab)\n",
    "    \n",
    "    int_text = np.array([vocab_to_int[word] for word in text if word != '\\n'])\n",
    "\n",
    "    pickle.dump((int_text, vocab_to_int, int_to_vocab), open('preprocess.p', 'wb'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 处理所有数据并保存\n",
    "将每期结果按照从第一期开始的顺序保存到文件中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "create_lookup_tables()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "# 读取保存的数据\n",
    "int_text, vocab_to_int, int_to_vocab = pickle.load(open('preprocess.p', mode='rb'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_batches(int_text, batch_size, seq_length):\n",
    "\n",
    "    batchCnt = len(int_text) // (batch_size * seq_length)\n",
    "    int_text_inputs = int_text[:batchCnt * (batch_size * seq_length)]\n",
    "    int_text_targets = int_text[1:batchCnt * (batch_size * seq_length)+1]\n",
    "\n",
    "    result_list = []\n",
    "    x = np.array(int_text_inputs).reshape(1, batch_size, -1)\n",
    "    y = np.array(int_text_targets).reshape(1, batch_size, -1)\n",
    "\n",
    "    x_new = np.dsplit(x, batchCnt)\n",
    "    y_new = np.dsplit(y, batchCnt)\n",
    "\n",
    "    for ii in range(batchCnt):\n",
    "        x_list = []\n",
    "        x_list.append(x_new[ii][0])\n",
    "        x_list.append(y_new[ii][0])\n",
    "        result_list.append(x_list)\n",
    "\n",
    "    return np.array(result_list)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练神经网络\n",
    "### 超参数\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "vocab_size = len(int_to_vocab)\n",
    "\n",
    "# 批次大小\n",
    "batch_size = 32  # 64\n",
    "# RNN的大小（隐藏节点的维度）\n",
    "rnn_size = 1000\n",
    "# 嵌入层的维度\n",
    "embed_dim = 256  # 这里做了调整，跟彩票预测的也不同了\n",
    "# 序列的长度\n",
    "seq_length = 15  # 注意这里已经不是1了，在古诗预测里面这个数值可以大一些，比如100也可以的\n",
    "\n",
    "save_dir = './save'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建计算图\n",
    "使用实现的神经网络构建计算图。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import datetime\n",
    "from tensorflow import keras\n",
    "from tensorflow.python.ops import summary_ops_v2\n",
    "import time\n",
    "\n",
    "MODEL_DIR = \"./poetry_models\"\n",
    "\n",
    "train_batches = get_batches(int_text, batch_size, seq_length)  \n",
    "losses = {'train': [], 'test': []}\n",
    "\n",
    "\n",
    "class poetry_network(object):\n",
    "    def __init__(self, batch_size=32):\n",
    "        self.batch_size = batch_size  \n",
    "        self.best_loss = 9999\n",
    "\n",
    "        self.model = tf.keras.Sequential([\n",
    "            tf.keras.layers.Embedding(vocab_size, embed_dim,\n",
    "                                      batch_input_shape=[batch_size, None]),\n",
    "            tf.keras.layers.LSTM(rnn_size,\n",
    "                                 return_sequences=True,\n",
    "                                 stateful=True,\n",
    "                                 recurrent_initializer='glorot_uniform'),\n",
    "            tf.keras.layers.Dense(vocab_size)\n",
    "        ])\n",
    "        self.model.summary()\n",
    "\n",
    "        self.optimizer = tf.keras.optimizers.Adam()\n",
    "        self.ComputeLoss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)\n",
    "\n",
    "        if tf.io.gfile.exists(MODEL_DIR):\n",
    "            #             print('Removing existing model dir: {}'.format(MODEL_DIR))\n",
    "            #             tf.io.gfile.rmtree(MODEL_DIR)\n",
    "            pass\n",
    "        else:\n",
    "            tf.io.gfile.makedirs(MODEL_DIR)\n",
    "\n",
    "        train_dir = os.path.join(MODEL_DIR, 'summaries', 'train')\n",
    "\n",
    "        self.train_summary_writer = summary_ops_v2.create_file_writer(train_dir, flush_millis=10000)\n",
    " \n",
    "        checkpoint_dir = os.path.join(MODEL_DIR, 'checkpoints')\n",
    "        self.checkpoint_prefix = os.path.join(checkpoint_dir, 'ckpt')\n",
    "        self.checkpoint = tf.train.Checkpoint(model=self.model, optimizer=self.optimizer)\n",
    "\n",
    "        # Restore variables on creation if a checkpoint exists.\n",
    "        self.checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))\n",
    "\n",
    "    @tf.function\n",
    "    def train_step(self, x, y):\n",
    "        # Record the operations used to compute the loss, so that the gradient\n",
    "        # of the loss with respect to the variables can be computed.\n",
    "\n",
    "        with tf.GradientTape() as tape:\n",
    "            logits = self.model(x, training=True)\n",
    "\n",
    "            loss = self.ComputeLoss(y, logits)\n",
    "\n",
    "        grads = tape.gradient(loss, self.model.trainable_variables)\n",
    "        self.optimizer.apply_gradients(zip(grads, self.model.trainable_variables))\n",
    "        return loss, logits\n",
    "\n",
    "    def training(self, epochs=1, log_freq=50):\n",
    "\n",
    "        batchCnt = len(int_text) // (batch_size * seq_length)\n",
    "        print(\"batchCnt : \", batchCnt)\n",
    "        for i in range(epochs):\n",
    "            train_start = time.time()\n",
    "            with self.train_summary_writer.as_default():\n",
    "                start = time.time()\n",
    "                # Metrics are stateful. They accumulate values and return a cumulative\n",
    "                # result when you call .result(). Clear accumulated values with .reset_states()\n",
    "                avg_loss = tf.keras.metrics.Mean('loss', dtype=tf.float32)\n",
    "\n",
    "                # Datasets can be iterated over like any other Python iterable.\n",
    "                for batch_i, (x, y) in enumerate(train_batches):\n",
    "                    loss, logits = self.train_step(x, y)\n",
    "                    avg_loss(loss)\n",
    "                    losses['train'].append(loss)\n",
    "\n",
    "                    if tf.equal(self.optimizer.iterations % log_freq, 0):\n",
    "                        summary_ops_v2.scalar('loss', avg_loss.result(), step=self.optimizer.iterations)\n",
    "\n",
    "                        rate = log_freq / (time.time() - start)\n",
    "                        print('Step #{}\\tLoss: {:0.6f} ({} steps/sec)'.format(\n",
    "                            self.optimizer.iterations.numpy(), loss, rate))\n",
    "\n",
    "                        avg_loss.reset_states()\n",
    "\n",
    "                        start = time.time()\n",
    "#                         self.checkpoint.save(self.checkpoint_prefix)\n",
    "            self.checkpoint.save(self.checkpoint_prefix)\n",
    "            print(\"save model\\n\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "在预处理过的数据上训练神经网络。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "embedding (Embedding)        (32, None, 256)           932352    \n",
      "_________________________________________________________________\n",
      "lstm (LSTM)                  (32, None, 1000)          5028000   \n",
      "_________________________________________________________________\n",
      "dense (Dense)                (32, None, 3642)          3645642   \n",
      "=================================================================\n",
      "Total params: 9,605,994\n",
      "Trainable params: 9,605,994\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n",
      "batchCnt :  161\n",
      "Step #50\tLoss: 6.623273 (1.4579346911925 steps/sec)\n",
      "Step #100\tLoss: 6.618058 (1.760274296160389 steps/sec)\n",
      "Step #150\tLoss: 6.762348 (1.7745142395123255 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #200\tLoss: 6.397208 (2.2850820118818485 steps/sec)\n",
      "Step #250\tLoss: 6.251562 (1.8047937182515517 steps/sec)\n",
      "Step #300\tLoss: 6.305763 (1.789508945441332 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #350\tLoss: 5.919520 (3.1871242340191173 steps/sec)\n",
      "Step #400\tLoss: 6.067682 (1.8248242920336322 steps/sec)\n",
      "Step #450\tLoss: 5.901038 (1.7958732812674587 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #500\tLoss: 5.770956 (5.331139954066546 steps/sec)\n",
      "Step #550\tLoss: 5.876189 (1.8298388242609862 steps/sec)\n",
      "Step #600\tLoss: 5.705443 (1.8219332897392602 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #650\tLoss: 5.654297 (14.827919861502984 steps/sec)\n",
      "Step #700\tLoss: 5.469127 (1.8231442647208338 steps/sec)\n",
      "Step #750\tLoss: 5.337306 (1.8267434745734867 steps/sec)\n",
      "Step #800\tLoss: 5.375128 (1.828701045539195 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #850\tLoss: 5.322458 (2.0248018022500203 steps/sec)\n",
      "Step #900\tLoss: 5.215079 (1.8239667744480133 steps/sec)\n",
      "Step #950\tLoss: 5.123084 (1.834375892411234 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #1000\tLoss: 4.967107 (2.672128916071813 steps/sec)\n",
      "Step #1050\tLoss: 5.040834 (1.8317109614828275 steps/sec)\n",
      "Step #1100\tLoss: 4.708903 (1.8279894869196698 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #1150\tLoss: 4.816755 (3.961418317687369 steps/sec)\n",
      "Step #1200\tLoss: 4.850560 (1.8087834436809638 steps/sec)\n",
      "Step #1250\tLoss: 4.394843 (1.7945373996503156 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #1300\tLoss: 4.411910 (7.777964661225948 steps/sec)\n",
      "Step #1350\tLoss: 4.418208 (1.866035664288443 steps/sec)\n",
      "Step #1400\tLoss: 4.252839 (1.861408189478152 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #1450\tLoss: 4.454759 (87.26839348742836 steps/sec)\n",
      "Step #1500\tLoss: 4.111177 (1.8460105203706052 steps/sec)\n",
      "Step #1550\tLoss: 3.907109 (1.8589639031597338 steps/sec)\n",
      "Step #1600\tLoss: 3.764209 (1.8525394819146817 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #1650\tLoss: 3.714081 (2.272515400431482 steps/sec)\n",
      "Step #1700\tLoss: 3.651021 (1.850448405181734 steps/sec)\n",
      "Step #1750\tLoss: 3.593717 (1.8405461966035317 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #1800\tLoss: 3.454843 (3.2069491401274033 steps/sec)\n",
      "Step #1850\tLoss: 3.560735 (1.8634316946331024 steps/sec)\n",
      "Step #1900\tLoss: 3.353009 (1.837472615800618 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #1950\tLoss: 3.191583 (5.070714659021573 steps/sec)\n",
      "Step #2000\tLoss: 2.994326 (1.8740099441504197 steps/sec)\n",
      "Step #2050\tLoss: 2.967816 (1.8570179454006341 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #2100\tLoss: 2.912447 (13.099227482673358 steps/sec)\n",
      "Step #2150\tLoss: 2.750483 (1.8570292423627217 steps/sec)\n",
      "Step #2200\tLoss: 2.703347 (1.8404420131698522 steps/sec)\n",
      "Step #2250\tLoss: 2.621744 (1.8543642597858263 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #2300\tLoss: 2.384810 (2.0068145794569348 steps/sec)\n",
      "Step #2350\tLoss: 2.460223 (1.8455290612798843 steps/sec)\n",
      "Step #2400\tLoss: 2.220877 (1.7498722047556878 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #2450\tLoss: 2.209287 (2.6324727261009984 steps/sec)\n",
      "Step #2500\tLoss: 2.146820 (1.8257173125613044 steps/sec)\n",
      "Step #2550\tLoss: 2.009501 (1.805184617147491 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #2600\tLoss: 2.056402 (3.775906589869417 steps/sec)\n",
      "Step #2650\tLoss: 2.150129 (1.8379271038044795 steps/sec)\n",
      "Step #2700\tLoss: 2.036724 (1.8441704010242608 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #2750\tLoss: 1.758791 (7.163476691200052 steps/sec)\n",
      "Step #2800\tLoss: 1.509837 (1.8478383593977727 steps/sec)\n",
      "Step #2850\tLoss: 1.598945 (1.8574515217262146 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #2900\tLoss: 1.455016 (44.37710654304331 steps/sec)\n",
      "Step #2950\tLoss: 1.495775 (1.873933534777757 steps/sec)\n",
      "Step #3000\tLoss: 1.536465 (1.8449590820022321 steps/sec)\n",
      "Step #3050\tLoss: 1.152052 (1.8595503360372683 steps/sec)\n",
      "save model\n",
      "\n",
      "Step #3100\tLoss: 1.178612 (2.2205148097444205 steps/sec)\n",
      "Step #3150\tLoss: 1.093991 (1.8545519570255493 steps/sec)\n",
      "Step #3200\tLoss: 1.204225 (1.8409946268711745 steps/sec)\n",
      "save model\n",
      "\n"
     ]
    }
   ],
   "source": [
    "net = poetry_network()\n",
    "net.training(20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 显示训练Loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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X77GKR2r/DjHjh2TJLVo0cLj9d6OV0bpPVq1auXxfOnxo0ePSipeeV68eLGefPJJTZ8+XTNmzCibyz333KOxY8eWrVi/+eab6t69uz788EM988wzkqTIyEhdfvnlevHFF0Ou0woB24swp4BdcXxmy3QAQKirqbKL2qL84pqrcePGKSEhQStWrFCnTp2czm3evFkLFy4M9vQC0rBh8Z6A+/fv93je2/HKaNSokSQpPT3d4/l9+/Y5XSdJHTt21IcffiiHw6E1a9boxx9/1GuvvabHHntM4eHhGjt2rKTiMP3II4/okUceUXp6uubOnauPP/5YX375pTZs2KBVq1b5/WBodaBExIvy32J+1WCTrwEAqJMKCwu1c+dO9enTxy1cFxQUhHy4lqRTTz1VERERSk1NVW5urtv5efPmBXyPvn37SpJ+/vlnt3N5eXlauHChjDEeN/cJCwvTaaedpvvvv1/Tp0+XJK/t91q2bKkxY8Zo6tSp6t+/v9auXastW7YEPH87EbC9cFrB9qMAhIccAQComyIiItSmTRutXbvWqWOFw+HQX/7yF23fvr0GZ+efhIQEjR49WgcOHNALL7zgdG7x4sX64osvAr7HVVddpQYNGuj9998vq7Mu9eyzz2rfvn1l/bElafXq1R47gJSupsfFxUkq7ile/oHNUnl5eWVlKZ4elKxJlIh44fSQox8PMLKCDQBA3XX//ffroYce0mmnnaYrr7xSYWFh+uWXX7Rjxw5dfPHF+vbbb2t6ihV68cUXNW/ePD3++OOaM2eOzjzzTKWlpenzzz/XZZddpilTpigsrOprr4mJiXrrrbd0ww036Oyzz9aYMWPUpk0bLVq0SLNnz1ZycrJee+21suunTZump556SoMGDVLXrl2VlJSknTt3aurUqQoPD9dDDz0kqbhme8CAAerevbv69u2r5ORkZWdn67vvvtPmzZt17bXXKjk5OeA/HzsRsL0oH7D9qa/2p9MIAAConR544AE1aNBAr732mt577z3Fx8dr6NCh+vzzz/X222/XioCdnJysRYsW6S9/+YtmzpypefPmqWfPnvrwww+Vk5OjKVOmlNVqV9U111yj5ORkTZgwQdOnT1dmZqZat26tP//5zxo3bpyaN29edu3ll1+ugwcPau7cufrqq6904sQJtWrVSpdddpkefPDBsg4pTZs21T/+8Q/N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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 250,
       "width": 364
      },
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "%config InlineBackend.figure_format = 'retina'\n",
    "# import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.plot(losses['train'], label='Training loss')\n",
    "plt.legend()\n",
    "_ = plt.ylim()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加载保存的模型，准备预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential_1\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "embedding_1 (Embedding)      (1, None, 256)            932352    \n",
      "_________________________________________________________________\n",
      "lstm_1 (LSTM)                (1, None, 1000)           5028000   \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (1, None, 3642)           3645642   \n",
      "=================================================================\n",
      "Total params: 9,605,994\n",
      "Trainable params: 9,605,994\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "restore_net=poetry_network(1)\n",
    "restore_net.model.build(tf.TensorShape([1, None]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 生成古诗\n",
    "开始生成古诗了。\n",
    " - `prime_word` 是开始的头一个字。\n",
    " - `top_n` 从前N个候选汉字中随机选择\n",
    " - `rule` 默认是7言绝句\n",
    " - `sentence_lines` 生成几句古诗，默认是4句（，和。都算一句）\n",
    " - `hidden_head` 藏头诗的前几个字"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gen_poetry(prime_word='白', top_n=5, rule=7, sentence_lines=4, hidden_head=None):\n",
    "    gen_length = sentence_lines * (rule + 1) - len(prime_word)\n",
    "    gen_sentences = [prime_word] if hidden_head==None else [hidden_head[0]]\n",
    "    temperature = 1.0\n",
    "\n",
    "    dyn_input = [vocab_to_int[s] for s in prime_word]\n",
    "    dyn_input = tf.expand_dims(dyn_input, 0)\n",
    "\n",
    "    dyn_seq_length = len(dyn_input[0])\n",
    "\n",
    "    restore_net.model.reset_states()\n",
    "    index=len(prime_word) if hidden_head==None else 1\n",
    "    for n in range(gen_length):\n",
    "\n",
    "        index += 1\n",
    "        predictions = restore_net.model(np.array(dyn_input))\n",
    "        \n",
    "        predictions = tf.squeeze(predictions, 0)\n",
    "\n",
    "        if index!=0 and (index % (rule+1)) == 0:\n",
    "            if ((index / (rule+1)) + 1) % 2 == 0:\n",
    "                predicted_id=vocab_to_int['，']\n",
    "            else:\n",
    "                predicted_id=vocab_to_int['。']\n",
    "        else:\n",
    "            if hidden_head != None and (index-1)%(rule+1)==0 and (index-1)//(rule+1) < len(hidden_head):\n",
    "                predicted_id=vocab_to_int[hidden_head[(index-1)//(rule+1)]]\n",
    "            else:\n",
    "                while True:\n",
    "                    predictions = predictions / temperature\n",
    "                    predicted_id = tf.random.categorical(predictions, num_samples=1)[-1, 0].numpy()\n",
    "\n",
    "                    # p = np.squeeze(predictions[-1].numpy())\n",
    "                    # p[np.argsort(p)[:-top_n]] = 0\n",
    "                    # p = p / np.sum(p)\n",
    "                    # c = np.random.choice(vocab_size, 1, p=p)[0]\n",
    "                    # predicted_id=c\n",
    "                    if(predicted_id != vocab_to_int['，'] and predicted_id != vocab_to_int['。'] ):\n",
    "                        break\n",
    "    # using a multinomial distribution to predict the word returned by the model\n",
    "    #         predictions = predictions / temperature\n",
    "    #         predicted_id = tf.multinomial(predictions, num_samples=1)[-1,0].numpy()\n",
    "\n",
    "        dyn_input = tf.expand_dims([predicted_id], 0)\n",
    "        gen_sentences.append(int_to_vocab[predicted_id])\n",
    "\n",
    "    poetry_script = ' '.join(gen_sentences)\n",
    "    poetry_script = poetry_script.replace('\\n ', '\\n')\n",
    "    poetry_script = poetry_script.replace('( ', '(')\n",
    "\n",
    "    return poetry_script"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 给定开头"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'白日依山尽 ， 照 岸 终 年 矜 。 目 尽 飞 猿 笑 ， 愁 长 断 独 声 。'"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gen_poetry(prime_word='白日依山尽', top_n=10, rule=5, sentence_lines=4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7言绝句"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 168,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'春 至 处 马 上 长 杨 ， 谁 家 窈 窕 九 重 闺 。 秦 国 陇 台 汉 边 风 ， 日 照 胡 沙 尘 花 中 。'"
      ]
     },
     "execution_count": 168,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gen_poetry(prime_word='春', top_n=10, rule=7, sentence_lines=4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5言"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 198,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'月 来 长 河 闭 ， 苔 疏 连 叶 满 。 归 来 入 汉 使 ， 居 带 天 门 台 。'"
      ]
     },
     "execution_count": 198,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gen_poetry(prime_word='月', top_n=10, rule=5, sentence_lines=4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 藏头诗"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 192,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'花裛茗蹙蕣觜聂，好嚬偷逮嚬鲤蜘。月擐拖鸷标拖鸷，圆偷选偷袄鸷蕣。'"
      ]
     },
     "execution_count": 192,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gen_poetry(prime_word='夏', top_n=10, rule=7, sentence_lines=4, hidden_head='花好月圆')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 结论\n",
    "\n",
    "使用原始的数据集训练太慢了，我只使用了前1000行的古诗训练，训练次数也不太多，可以继续训练再试试。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "今天的分享就到这里，大家洗洗睡吧 ：）"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
